package scraper

import (
	"encoding/json"
	"errors"
	"fmt"
	"html"
	"io"
	"net/http"
	"regexp"
	"strings"
	"time"

	"spam3uk/internal/models"
	"spam3uk/internal/storage"

	"github.com/sirupsen/logrus"
)

// VehicleScraper scrapes Ford UK vehicles from public showroom pages
type VehicleScraper struct {
	storage storage.VehicleStorage
	logger  *logrus.Logger
	client  *http.Client
	baseURL string
}

func NewVehicleScraper(storage storage.VehicleStorage, logger *logrus.Logger, baseURL string) *VehicleScraper {
	// 配置HTTP客户端，禁用HTTP/2以避免连接问题
	tr := &http.Transport{
		ForceAttemptHTTP2:     false, // 禁用HTTP/2
		MaxIdleConns:          10,
		IdleConnTimeout:       30 * time.Second,
		DisableCompression:    false,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
	}

	client := &http.Client{
		Transport: tr,
		Timeout:   60 * time.Second, // 增加超时时间
	}

	return &VehicleScraper{
		storage: storage,
		logger:  logger,
		baseURL: baseURL,
		client:  client,
	}
}

// ScrapeAll scrapes cars and vans-and-pickups pages
func (vs *VehicleScraper) ScrapeAll() error {
	if err := vs.ScrapeCarsPage(vs.baseURL + "/cars"); err != nil {
		return err
	}
	// Try vans & pickups (ignore error to proceed)
	_ = vs.ScrapeCarsPage(vs.baseURL + "/vans-and-pickups")
	return nil
}

// ScrapeCarsPage fetches a showroom page and upserts nameplates into DB
func (vs *VehicleScraper) ScrapeCarsPage(pageURL string) error {
	vs.logger.WithField("url", pageURL).Info("Scraping showroom page")

	req, _ := http.NewRequest("GET", pageURL, nil)
	// 强制使用HTTP/1.1
	req.Proto = "HTTP/1.1"
	req.ProtoMajor = 1
	req.ProtoMinor = 1

	req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
	req.Header.Set("Accept-Language", "en-US,en;q=0.5")
	req.Header.Set("Connection", "close") // 强制关闭连接

	resp, err := vs.client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to fetch %s: %w", pageURL, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("unexpected status %d for %s", resp.StatusCode, pageURL)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("failed to read body: %w", err)
	}

	// Prefer parsing data-nameplates attribute, which contains JSON with all nameplates
	// Some pages embed JSON inside different attributes (e.g., mavConfiguration). Try multiple fallbacks.
	htmlText := string(body)
	// 支持单引号和双引号属性
	nameplatesJSON, err := extractDataAttribute(htmlText, "data-nameplates")
	if err != nil || nameplatesJSON == "" {
		// try acc-showroom config (mavConfiguration)
		nameplatesJSON, err = extractDataAttribute(htmlText, "data-mav-configuration")
	}
	if nameplatesJSON == "" || err != nil {
		// 进一步回退：尝试从 script 标签中解析 JSON 片段
		// 常见变体：window.__INITIAL_STATE__、mavConfiguration JSON
		if alt, ok := extractJSONFromScripts(htmlText); ok {
			nameplatesJSON = alt
		} else {
			return fmt.Errorf("failed to extract nameplates: %w", err)
		}
	}

	// Unescape HTML entities to get valid JSON
	nameplatesJSON = html.UnescapeString(nameplatesJSON)

	var nameplates []map[string]any
	if err := json.Unmarshal([]byte(nameplatesJSON), &nameplates); err != nil {
		return fmt.Errorf("failed to unmarshal nameplates json: %w", err)
	}

	vs.logger.WithField("count", len(nameplates)).Info("Parsed nameplates from showroom page")

	for _, np := range nameplates {
		vehicle := vs.convertNameplate(np, pageURL)
		if vehicle == nil || vehicle.Model == "" {
			continue
		}
		if err := vs.storage.UpsertVehicle(vehicle); err != nil {
			vs.logger.WithError(err).WithField("model", vehicle.Model).Warn("Upsert failed")
		}
	}

	return nil
}

// convertNameplate maps a nameplate map into VehicleInfo
func (vs *VehicleScraper) convertNameplate(np map[string]any, pageURL string) *models.VehicleInfo {
	// helpers
	getString := func(m map[string]any, key string) string {
		if v, ok := m[key]; ok {
			if s, ok2 := v.(string); ok2 {
				return s
			}
		}
		return ""
	}

	path := getString(np, "path")
	name := getString(np, "fullName")
	if name == "" {
		name = getString(np, "name")
	}
	defaultModelPath := getString(np, "defaultModelPath")

	// Optional fields: image and availability
	imageURL := getString(np, "image")
	if imageURL == "" {
		// some payloads use different key naming
		imageURL = getString(np, "imageUrl")
	}
	availability := getString(np, "availability")
	if availability == "" {
		// normalize from badges/labels if present
		if badges, ok := np["badges"].([]any); ok {
			for _, b := range badges {
				if bm, ok2 := b.(map[string]any); ok2 {
					label := getString(bm, "label")
					if label != "" && (strings.Contains(strings.ToLower(label), "coming") || strings.Contains(strings.ToLower(label), "soon")) {
						availability = "coming_soon"
						break
					}
				}
			}
		}
	}

	model := deriveModelFromPath(path)
	if model == "" && name != "" {
		model = deriveModelFromName(name)
	}

	category := deriveCategory(np, name, path)
	fuelType := deriveFuelType(np, name, path)

	vehicleType := "car"
	if strings.Contains(strings.ToLower(pageURL), "vans-and-pickups") {
		vehicleType = "van"
	}

	// Derive ford group from np or path/name
	fordGroup := deriveFordGroup(np, name, path)

	vehicle := &models.VehicleInfo{
		VehicleCategory: category,
		VehicleType:     vehicleType,
		FordGroup:       fordGroup,
		Make:            "Ford",
		Model:           model,
		Variant:         "",
		FuelType:        fuelType,
		BodyStyle:       "",
		ImageURL:        imageURL,
		DetailURL:       absolutize(vs.baseURL, path),
		TrimDetailLink:  absolutize(vs.baseURL, defaultModelPath),
		Description:     name,
		Availability:    availability,
		SourcePage:      pageURL,
		ScrapedAt:       time.Now(),
		CreatedAt:       time.Now(),
		UpdatedAt:       time.Now(),
	}

	return vehicle
}

// Helper: extract single-quoted data-attr JSON
func extractDataAttribute(htmlText, attr string) (string, error) {
	// 优先匹配单引号
	reSingle := regexp.MustCompile(attr + `='([^']*)'`)
	if m := reSingle.FindStringSubmatch(htmlText); len(m) >= 2 {
		return m[1], nil
	}
	// 回退匹配双引号
	reDouble := regexp.MustCompile(attr + `="([^"]*)"`)
	if m := reDouble.FindStringSubmatch(htmlText); len(m) >= 2 {
		return m[1], nil
	}
	return "", errors.New("attribute not found: " + attr)
}

// extractJSONFromScripts 尝试从页面脚本中提取 showroom 配置 JSON
func extractJSONFromScripts(htmlText string) (string, bool) {
	// 粗略匹配常见键并尽量截取一个 JSON 数组或对象
	// data-nameplates 常见是数组
	reArray := regexp.MustCompile(`data-nameplates\"?\s*[:=]\s*(\[.*?\])`)
	if m := reArray.FindStringSubmatch(htmlText); len(m) >= 2 {
		return html.UnescapeString(m[1]), true
	}
	// mavConfiguration/nameplates 也可能存在
	reObj := regexp.MustCompile(`mavConfiguration\"?\s*[:=]\s*\{[\s\S]*?\}`)
	if m := reObj.FindString(htmlText); m != "" {
		// 再次从对象中抽取 nameplates 数组
		reInner := regexp.MustCompile(`nameplates\"?\s*[:=]\s*(\[.*?\])`)
		if mm := reInner.FindStringSubmatch(m); len(mm) >= 2 {
			return html.UnescapeString(mm[1]), true
		}
	}
	return "", false
}

func absolutize(base, p string) string {
	if p == "" {
		return ""
	}
	if strings.HasPrefix(p, "http://") || strings.HasPrefix(p, "https://") {
		return p
	}
	if strings.HasPrefix(p, "/") {
		return strings.TrimRight(base, "/") + p
	}
	return strings.TrimRight(base, "/") + "/" + p
}

func deriveModelFromPath(path string) string {
	parts := strings.Split(strings.Trim(path, "/"), "/")
	if len(parts) == 0 {
		return ""
	}
	last := parts[len(parts)-1]
	last = strings.ReplaceAll(last, "electric-", "")
	last = strings.ReplaceAll(last, "all-electric-", "")
	tokens := strings.Split(last, "-")
	for i := range tokens {
		if tokens[i] == "mach" && i+1 < len(tokens) && tokens[i+1] == "e" {
			tokens[i+1] = "Mach-E" // keep hyphenated form later
		}
		if len(tokens[i]) > 0 {
			tokens[i] = strings.ToUpper(tokens[i][:1]) + strings.ToLower(tokens[i][1:])
		}
	}
	model := strings.Join(tokens, " ")
	model = strings.ReplaceAll(model, "Mach E", "Mach-E")
	model = strings.ReplaceAll(model, "Gen E", "Gen-E")
	return strings.TrimSpace(model)
}

func deriveModelFromName(name string) string {
	n := strings.TrimSpace(name)
	candidates := []string{"Puma Gen-E", "Puma", "Capri", "Explorer", "Mustang Mach-E", "Ranger", "Transit", "E-Transit", "Tourneo", "Nugget"}
	for _, c := range candidates {
		if strings.Contains(strings.ToLower(n), strings.ToLower(c)) {
			return c
		}
	}
	fields := strings.Fields(n)
	if len(fields) > 0 {
		return fields[len(fields)-1]
	}
	return n
}

func deriveCategory(np map[string]any, name, path string) string {
	if vcl, ok := np["vehicleCategoriesList"].([]any); ok {
		var cats []string
		for _, v := range vcl {
			if m, ok2 := v.(map[string]any); ok2 {
				if c, ok3 := m["category"].(string); ok3 {
					cats = append(cats, c)
				}
			}
		}
		if contains(cats, "hev") {
			return "hybrid_electric"
		}
		if contains(cats, "suv") {
			return "suv_crossovers"
		}
		if contains(cats, "performance") {
			return "performance"
		}
		if contains(cats, "family") {
			return "people_movers"
		}
	}
	lp := strings.ToLower(name + " " + path)
	switch {
	case strings.Contains(lp, "electric") || strings.Contains(lp, "mach-e"):
		return "hybrid_electric"
	case strings.Contains(lp, "explorer") || strings.Contains(lp, "puma") || strings.Contains(lp, "ranger") || strings.Contains(lp, "suv"):
		return "suv_crossovers"
	case strings.Contains(lp, "mustang") || strings.Contains(lp, "raptor") || strings.Contains(lp, "gtd"):
		return "performance"
	case strings.Contains(lp, "tourneo") || strings.Contains(lp, "transit"):
		return "people_movers"
	default:
		return "suv_crossovers"
	}
}

// deriveFordGroup maps showroom grouping to normalized group names (small, fv, coming_soon, etc.)
func deriveFordGroup(np map[string]any, name, path string) string {
	// try explicit field
	if grp, ok := np["group"].(string); ok && grp != "" {
		return strings.ToLower(grp)
	}
	l := strings.ToLower(name + " " + path)
	switch {
	case strings.Contains(l, "coming-soon") || strings.Contains(l, "coming soon"):
		return "coming_soon"
	case strings.Contains(l, "small"):
		return "small"
	case strings.Contains(l, "fv"):
		return "fv"
	default:
		return ""
	}
}

func deriveFuelType(np map[string]any, name, path string) string {
	if htl, ok := np["hevTechnologyList"].([]any); ok {
		for _, v := range htl {
			if m, ok2 := v.(map[string]any); ok2 {
				if enabled, _ := m["enabled"].(bool); enabled {
					if label, _ := m["label"].(string); label != "" {
						return label
					}
				}
			}
		}
	}
	l := strings.ToLower(name + " " + path)
	switch {
	case strings.Contains(l, "electric") || strings.Contains(l, "mach-e"):
		return "Electric"
	case strings.Contains(l, "hybrid"):
		return "Hybrid"
	default:
		return ""
	}
}

func contains(arr []string, s string) bool {
	for _, v := range arr {
		if v == s {
			return true
		}
	}
	return false
}
